package jmine.tec.script.impl.velocity;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.Map;

import jmine.tec.annotations.Unmodifiable;
import jmine.tec.script.RenderResult;
import jmine.tec.script.ScriptExecutionContext;
import jmine.tec.script.ScriptExecutorException;
import jmine.tec.script.impl.AbstractScriptExecutor;
import jmine.tec.utils.io.EncodingUtils;

import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;

/**
 * Executor de scripts velocity
 * 
 * @author lundberg
 */
@Unmodifiable
public class VelocityScriptExecutor extends AbstractScriptExecutor {

    public static final String TEMPLATE_ENCODING = "_encoding";

    private VelocityEngineBean velocityBean;

    /**
     * {@inheritDoc}
     */
    @Override
    protected <R> R doExecuteScript(ScriptExecutionContext<R> context) throws ScriptExecutorException {
        try {
            String nome = String.format("scriptTemplate_%s_%s", context.getScriptId(), context.getVersion());
            Template velocityTemplate = this.velocityBean.getTemplate(nome, context.getScript());
            VelocityContext velocityContext = new VelocityContext();
            this.putParameters(velocityContext, context.getParameters().getParameterValues());
            this.putParameters(velocityContext, context.getParameters().getConstants());
            R result = context.getResult();
            Writer writer = this.createWriter(result, EncodingUtils.UTF_CHARSET);
            velocityTemplate.merge(velocityContext, writer);
            writer.flush();
            return context.getResult();
        } catch (IOException e) {
            throw new VelocityScriptExecutorException(ScriptVelocityMessages.IOERROR.create(context.getScriptName()), e);
        } catch (ResourceNotFoundException e) {
            throw new VelocityScriptExecutorException(ScriptVelocityMessages.RESOURCE_NOT_FOUND.create(context.getScriptName()), e);
        } catch (ParseErrorException e) {
            throw new VelocityScriptExecutorException(ScriptVelocityMessages.PARSE_ERROR.create(context.getScriptName()), e);
        } catch (Exception e) {
            throw new VelocityScriptExecutorException(ScriptVelocityMessages.UNEXPECTED.create(context.getScriptName()), e);
        }
    }

    /**
     * Cria o writer para a execução do template. Caso o result seja um outputstream, o writer irá escrever nele. Caso contrário, será
     * criado um writer que joga fora o resultado da template.
     * 
     * @param <R> tipo de resultado do contexto
     * @param result resultado do contexto
     * @param encoding nome do charset
     * @return writer
     * @throws UnsupportedEncodingException UnsupportedEncodingException
     */
    private <R> Writer createWriter(R result, String encoding) throws UnsupportedEncodingException {
        Writer writer;
        if (result instanceof RenderResult) {
            writer = new BufferedWriter(new OutputStreamWriter(((RenderResult) result).getStream(), encoding));
        } else if (result instanceof OutputStream) {
            writer = new BufferedWriter(new OutputStreamWriter((OutputStream) result, encoding));
        } else {
            writer = new Writer() {
                /**
                 * {@inheritDoc}
                 */
                @Override
                public void write(char[] cbuf, int off, int len) throws IOException {
                    // OK
                }

                /**
                 * {@inheritDoc}
                 */
                @Override
                public void flush() throws IOException {
                    // OK
                }

                /**
                 * {@inheritDoc}
                 */
                @Override
                public void close() throws IOException {
                    // OK
                }
            };
        }
        return writer;
    }

    /**
     * Adiciona os parâmetros ao contexto velocity.
     * 
     * @param velocityContext velocityContext
     * @param parameters parameters
     */
    private void putParameters(VelocityContext velocityContext, Map<String, Object> parameters) {
        for (Map.Entry<String, Object> p : parameters.entrySet()) {
            velocityContext.put(p.getKey(), p.getValue());
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected <R> R doGetDependencies(ScriptExecutionContext<R> context) throws ScriptExecutorException {
        throw new UnsupportedOperationException();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected <R> R doGetValidations(ScriptExecutionContext<R> context) throws ScriptExecutorException {
        throw new UnsupportedOperationException();
    }

    /**
     * @param velocityBean the velocityBean to set
     */
    public void setVelocityBean(VelocityEngineBean velocityBean) {
        this.velocityBean = velocityBean;
    }

}
